﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using DNT.RavenQA.Core.Extensions;
using DNT.RavenQA.Core.Models;
using DNT.RavenQA.Web.Infrastructure.Extensions;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OpenId;
using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration;
using DotNetOpenAuth.OpenId.RelyingParty;
using Newtonsoft.Json;
using Raven.Client;

namespace DNT.RavenQA.Web.Infrastructure.Security
{
    public class OpenIdAuthenticationService : IOpenIdAuthenticationService
    {
        private readonly IDocumentSession _session;
        private readonly OpenIdRelyingParty _relyingParty;

        public OpenIdAuthenticationService(IDocumentSession session)
        {
            session.EnsureIsNotNull("session");
            _session = session;

            _relyingParty = new OpenIdRelyingParty();
        }

        public ActionResult GetRedirectingResponse(String providerName, String returnUrl)
        {
            providerName.EnsureIsNotNullOrEmpty("identifier");

            var identifier = Identifier.Parse(providerName);
            var request = _relyingParty.CreateRequest(identifier);

            if (returnUrl.IsNotNull())
                request.AddCallbackArguments("ReturnUrl", returnUrl);

            var fields = new ClaimsRequest
                             {
                                 Email = DemandLevel.Require,
                                 FullName = DemandLevel.Require,
                                 Nickname = DemandLevel.Require
                             };
            request.AddExtension(fields);

            return request.RedirectingResponse.AsActionResult();
        }

        public Boolean TryProcessOpenAuthResponse(HttpContextBase context)
        {
            var response = _relyingParty.GetResponse();

            if (response.IsNull())
                return false;

            if (!response.IsSuccessful())
                throw Exceptions[response.Status]();

            var user = OpenIdUser.Build(response);
            var dbUser = _session.Query<User>()
                .SingleOrDefault(x => x.ProviderName == user.ProviderName &&
                                      x.EmailAddress == user.EmailAddress);

            if (dbUser.IsNull())
                _session.Store(user);
            else
                user = dbUser;

            GenerateAndStoreCookie(user, context);
            return true;
        }

        private static void GenerateAndStoreCookie(User user, HttpContextBase context)
        {
            user.EnsureIsNotNull("user");
            context.EnsureIsNotNull("context");

            var userData = JsonConvert.SerializeObject(new
                    {
                        user.EmailAddress,
                        user.ProviderName
                    });
            var now = DateTime.Now;
            var ticket = new FormsAuthenticationTicket(1,
                                                       user.Name,
                                                       now,
                                                       now.AddDays(7),
                                                       true,
                                                       userData);
            context.Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName,
                                                        FormsAuthentication.Encrypt(ticket)));
        }

        private static readonly IDictionary<AuthenticationStatus, Func<Exception>> Exceptions = new Dictionary<AuthenticationStatus, Func<Exception>>
                                                                                                    {
                                                                                                        {AuthenticationStatus.Canceled, () => new AuthenticationCanceledByUserException()},
                                                                                                        {AuthenticationStatus.Failed, () => new AuthenticationFailedException()}
                                                                                                    };
    }
}